﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Xml;
using Xant.Querier.Core;


namespace Xant.Querier.Compilers
{
    public class EntityConfiguration
    {
        //private string entityName;
        //private string keyField;
        //private string tableName;
        protected string tableAlias;
        private readonly EntityConfigurationCollection<RelationshipConfig> relationships = new EntityConfigurationCollection<RelationshipConfig>();

        public EntityConfiguration Parent { get; protected set; }
        /// <summary>
        /// 名称(实体配置类中此属性记录实体类的类名，实体关系配置类中此属性记录实体导航属性的名称)
        /// </summary>
        public string Name { get; private set; }
        //public string KeyField { get; private set; }
        /// <summary>
        /// 数据库物理表名
        /// </summary>
        public string TableName { get; private set; }

        /// <summary>
        /// 表别名
        /// </summary>
        public string TableAlias
        {
            get { return string.IsNullOrEmpty(tableAlias) ? TableName : tableAlias; }
            set { this.tableAlias = value; }
        }

        /// <summary>
        /// 实体关系列表
        /// <remarks>本来应该返回只读属性如ReadOnlyCollection，但是这样就没办法利用EntityConfigurationCollection类提供的索引器来方便的根据名字访问配置项了</remarks>
        /// </summary>
        public EntityConfigurationCollection<RelationshipConfig> Relationships
        {
            get { return relationships; }
        }

        public EntityConfiguration(string entityName, /*string keyField,*/ string tableName, string tableAlias)
        {
            if (string.IsNullOrEmpty(entityName))
            {
                throw new ArgumentNullException("entityName");
            }
            if (string.IsNullOrEmpty(tableName))
            {
                throw new ArgumentNullException("tableName");
            }
            this.Name = entityName;
            //this.KeyField = keyField;
            this.TableName = tableName;
            this.TableAlias = tableAlias;
        }

        public EntityConfiguration(string entityName, /*string keyField,*/ string tableName)
            : this(entityName, /*keyField,*/ tableName, null)
        {
        }

        public void AddRelationship(RelationshipConfig relationship)
        {
            if (
                !relationships.Exists(
                    p => p.Name.Equals(relationship.Name, StringComparison.InvariantCultureIgnoreCase)))
            {
                relationships.Add(relationship);
                relationship.Parent = this;
            }
        }

        public void AddRelationship(string propertyName, string referenceField, string relatedToField, string tableName,
            string tableAlias)
        {
            var relationship = new RelationshipConfig(propertyName, referenceField, relatedToField, tableName,
                tableAlias);
            AddRelationship(relationship);
        }

    }

    public class RelationshipConfig: EntityConfiguration
    {
        /// <summary>
        /// 参考字段(即外键列)
        /// </summary>
        public string ReferenceField { get; set; }
        /// <summary>
        /// 关联到上级实体对象的字段(即主键列)
        /// </summary>
        public string RelatedToField { get; set; }


        public RelationshipConfig(string propertyName, string referenceField, string relatedToField, string tableName,
            string tableAlias): base(propertyName, tableName, tableAlias)
        {
            this.ReferenceField = referenceField;
            this.RelatedToField = relatedToField;
        }

        public RelationshipConfig(string property, string referenceField, string relatedToField, string tableName)
            : this(property, referenceField, relatedToField, tableName, null)
        {
        }
    }

    public class EntityConfigurationCollection<T> : List<T> where T:EntityConfiguration
    {
        public bool Contains(string entityName)
        {
            return this.Exists(p => p.Name.Equals(entityName));
        }

        public int IndexOf(string entityName)
        {
            for (int i=0; i<base.Count; i++)
            {
                if (base[i].Name.Equals(entityName))
                    return i;
            }
            return -1;
        }

        public T Find(string entityName)
        {
            return this.FirstOrDefault(p => p.Name.Equals(entityName));
        }

        public T this[string name]
        {
            get
            {
                return Find(name);
            }
            set
            {
                var idx = IndexOf(name);
                if (idx < 0)
                {
                    base.Add(value);
                }
                else
                {
                    base[idx] = value;
                }
            }
        }

    }

    public static class EntityConfigurationReader
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlNode">必须保证xmlNode指向的XML节点为合法的实体配置节点，否则将引发异常</param>
        /// <returns></returns>
        public static EntityConfiguration ReadConfiguration(XmlNode xmlNode)
        {
            if (xmlNode == null || !xmlNode.Name.Equals("Entity") || xmlNode.Attributes == null)
            {
                throw new ArgumentException("xmlNode参数不是合法的实体配置节点", "xmlNode");
            }
            string entityName = null, tableName = null, tableAlias = null;
            if (xmlNode.Attributes["Name"] != null)
                entityName = xmlNode.Attributes["Name"].Value;
            if (xmlNode.Attributes["TableName"] != null)
                tableName = xmlNode.Attributes["TableName"].Value;
            if (xmlNode.Attributes["TableAlias"] != null)
                tableAlias = xmlNode.Attributes["TableAlias"].Value;
            var config = new EntityConfiguration(entityName, tableName, tableAlias);
            ReadRelationships(config, xmlNode);
            return config;
        }

        private static void ReadRelationships(EntityConfiguration config, XmlNode nodeParent)
        {
            var relationships = ReadRelationships(nodeParent.SelectNodes("Relationship[@Property]"));
            if (relationships != null && relationships.Any())
            {
                foreach (var relationship in relationships)
                {
                    config.AddRelationship(relationship);
                }
            }
            
        }

        private static IEnumerable<RelationshipConfig> ReadRelationships(XmlNodeList nodes)
        {
            var relationships = new List<RelationshipConfig>();
            foreach (XmlNode node in nodes)
            {
                var relationship = ReadRelationship(node);
                if (relationship != null)
                {
                    relationships.Add(relationship);
                }
            }
            return relationships;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlNode">必须保证xmlNode指向的XML节点为合法的实体关系配置节点，否则将引发异常</param>
        /// <returns></returns>
        private static RelationshipConfig ReadRelationship(XmlNode xmlNode)
        {
            if (xmlNode == null || !xmlNode.Name.Equals("Relationship") || xmlNode.Attributes == null)
            {
                throw new ArgumentException("xmlNode参数不是合法的实体关系配置节点", "xmlNode");
            }
            string propertyName = null,
                referenceField = null,
                relatedToField = null,
                tableName = null,
                tableAlias = null;
            if (xmlNode.Attributes["Property"] != null)
                propertyName = xmlNode.Attributes["Property"].Value;
            if (xmlNode.Attributes["ReferenceField"] != null)
                referenceField = xmlNode.Attributes["ReferenceField"].Value;
            if (xmlNode.Attributes["RelatedToField"] != null)
                relatedToField = xmlNode.Attributes["RelatedToField"].Value;
            if (xmlNode.Attributes["TableName"] != null)
                tableName = xmlNode.Attributes["TableName"].Value;
            if (xmlNode.Attributes["TableAlias"] != null)
                tableAlias = xmlNode.Attributes["TableAlias"].Value;
            var config = new RelationshipConfig(propertyName, referenceField, relatedToField, tableName, tableAlias);
            ReadRelationships(config, xmlNode);
            return config;
        }
    }

    public static class EntityConfigurationManager
    {
        private static readonly EntityConfigurationCollection<EntityConfiguration> EntityConfigs;

        /// <summary>
        /// 配置文件名称(从应用程序配置文件的AppSettings\EntityRelationshipConfigFile项目读取)
        /// </summary>
        public static string ConfigFileName { get; private set; }


        static EntityConfigurationManager()
        {
            EntityConfigs = new EntityConfigurationCollection<EntityConfiguration>();
            ConfigFileName = ConfigurationManager.AppSettings["EntityRelationshipConfigFile"];
            if (!string.IsNullOrEmpty(ConfigFileName) && File.Exists(ConfigFileName))
            {
                using (var fs = File.OpenRead(ConfigFileName))
                {
                    LoadConfiguration(fs);
                    fs.Close();
                }
            }
        }

        /// <summary>
        /// 将字典数据追加到现有的表字典中
        /// </summary>
        /// <param name="configs">字典的key值为对象名称，value值为数据库表名</param>
        /// <param name="autoReplace">自动替换原字典中已有项目(此参数值为false时，如果项目已存在于原字典中则引发异常)</param>
        public static void AppendConfiguration(IEnumerable<EntityConfiguration> configs, bool autoReplace)
        {
            //如果不允许自动替换，则先检测有无冲突，避免部分字典被添加后再引发异常
            if (!autoReplace)
            {
                foreach (var config in configs)
                {
                    if (EntityConfigs.Contains(config.Name))
                    {
                        throw new Exception(string.Format("entity [{0}] is reduplicated.", config.Name));
                    }
                }
            }
            foreach (var config in configs)
            {
                EntityConfigs[config.Name] = config;
            }
        }

        public static void AppendConfiguration(EntityConfiguration config, bool autoReplace)
        {
            //如果不允许自动替换，则先检测有无冲突
            if (!autoReplace && EntityConfigs.Contains(config.Name))
            {
                throw new Exception(string.Format("entity [{0}] is reduplicated.", config.Name));
            }
            EntityConfigs[config.Name] = config;
        }

        /// <summary>
        /// 从xml节点加载实体关系字典
        /// </summary>
        /// <param name="xmlNode">包含实体配置内容的xml节点</param>
        public static void LoadConfiguration(XmlNode xmlNode)
        {
            var config = EntityConfigurationReader.ReadConfiguration(xmlNode);
            AppendConfiguration(config, true);
        }

        public static void LoadConfiguration(XmlDocument doc, string entityTypeName = "")
        {
            string xpath = string.IsNullOrEmpty(entityTypeName) ? "//Entity[@Name]" : string.Format("//Entity[@Name='{0}']", entityTypeName);
            var xmlNodes = doc.SelectNodes(xpath);
            if (xmlNodes == null) return;
            foreach (XmlNode xmlNode in xmlNodes)
            {
                LoadConfiguration(xmlNode);
            }
        }

        public static void LoadConfiguration(string xml)
        {
            if (string.IsNullOrEmpty(xml))
                return;
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            LoadConfiguration(doc);
        }

        /// <summary>
        /// 从xml文件中加载数据表字典
        /// </summary>
        /// <param name="stream">包含xml配置内容的流对象</param>
        public static void LoadConfiguration(Stream stream)
        {
            var doc = new XmlDocument();
            doc.Load(stream);
            LoadConfiguration(doc);
        }

        public static void ClearEntityConfiguration()
        {
            EntityConfigs.Clear();
        }

        public static EntityConfiguration FindEntityConfiguration(string queryEntityType)
        {
            var config = EntityConfigs[queryEntityType];
            return config;
        }

        /// <summary>
        /// 查找与指定字段相关的实体配置项
        /// </summary>
        /// <param name="field">字段<see cref="Field"/></param>
        /// <param name="queryEntityType">查询的实体类型名(使用短名称或全名由使用方自行决定)</param>
        /// <returns>返回找到的配置项</returns>
        public static EntityConfiguration FindEntityConfiguration(this Field field, string queryEntityType)
        {
            var config = EntityConfigs[queryEntityType];
            if (!field.Path.Contains('.'))
            {
                return config;
            }
            var propertyQueue =
                new Queue<string>(field.Path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries));
            do
            {
                var propertyName = propertyQueue.Dequeue();
                var c = config.Relationships[propertyName];
                //注意：为了适应DDD(领域驱动设计)概念中的值对象，配置文件中可能就没有与值对象类匹配的配置项
                //例如，供应商类有一个值对象属性：地址，但是地址对象并不会对应到数据库中一个单独的物理表，而是同样存放在供应商表中。
                //所以，类似 Supplier.Address.Country="China" 这样条件在解析时，搜索不到名为"Address"的关系配置项时应直接跳过，沿用Supplier实体配置项，最终生成的SQL条件语句可能为：s.Country='China'。
                if (c != null)
                    config = c;
            } while (propertyQueue.Count > 1);
            return config;
        }

        
    }

}

